package com.android.videolib;

import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.os.Build;
import android.text.TextUtils;

import com.android.av.edit.AVMuxer;
import com.common.base.ComponentContext;
import com.common.base.log.DLog;
import com.common.base.utils.FileUtil;
import com.common.base.utils.thread.ThreadPool;
import com.photoeditor.demo.statistics.StatisticsUtils;
import com.photoeditor.demo.util.BitmapUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Created by winn on 17/8/31.
 */

public class VideoCooker {

    private static final String TAG = VideoCooker.class.getSimpleName();

    private static String sFfmpegFile;

    private static String TMP_DIR;
    private static String AUDIO_FILE_SUFFIX = ".aac";
    private static String VIDEO_FILE_SUFFIX = ".mp4";
    private static String IMG_FILE_SUFFIX = ".jpg";

    /**
     * 裁剪视频
     * @param srcFile 源文件路径
     * @param dstFile 生成后文件路径
     * @param start 裁剪起始时间
     * @param duration 裁剪时长（单位：秒）
     */
    public static void clipVideo(String srcFile, String dstFile, int start, int duration) {
        long time = System.currentTimeMillis();
        File file = new File(dstFile);
        if (file.exists()) {
            file.delete();
        }
        // ffmpeg -ss 0 -t 30 -accurate_seek -i src.mp4 -codec copy -avoid_negative_ts 1 cut.mp4
        ArrayList<String> cmdArray = new ArrayList<>();
        cmdArray.add(sFfmpegFile);
        cmdArray.add("-ss");
        cmdArray.add(String.valueOf(start));
        cmdArray.add("-t");
        cmdArray.add(String.valueOf(duration));
        cmdArray.add("-accurate_seek");
        cmdArray.add("-i");
        cmdArray.add(srcFile);
        cmdArray.add("-codec");
        cmdArray.add("copy");
        cmdArray.add("-avoid_negative_ts");
        cmdArray.add("1");
        cmdArray.add(dstFile);
        exeCmd(cmdArray);
        DLog.i(TAG, "clipVideo time:" + (System.currentTimeMillis() - time));
    }

    /**
     * 已一个视频为基础，制作一个指定分辨率，指定时长的视频
     * 耗时操作，需放在异步执行
     * @param srcFile       基础视频保存路径
     * @param dstFile       目标视频保存路径
     * @param width         目标视频宽度(单位：像素)
     * @param height        目标视频高度(单位：像素)
     * @param srcduring     原视频时长（单位：毫秒）
     * @param dstduring     目标视频时长（单位：毫秒）
     */
    public static void makeEmptyVideo(String srcFile, String dstFile, int width, int height, int srcduring, int dstduring) {
        long time = System.currentTimeMillis();
        String tmpFile = getTmpDir() + "scale" + System.currentTimeMillis() + VIDEO_FILE_SUFFIX;
        makeScaleVideo(srcFile, tmpFile, width, height);

        ArrayList<String> cmdArray = new ArrayList<>();
        int count = dstduring / srcduring + 1;
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < count; ++i) {
            builder.append("file '");
            builder.append(tmpFile);
            builder.append("'\n");
        }
        String concatFile = getTmpDir() + "concat" + System.currentTimeMillis() + ".txt";
        com.photoeditor.demo.util.FileUtil.saveStringToFile(builder.toString(), concatFile);

        cmdArray.add(sFfmpegFile);
        cmdArray.add("-safe");
        cmdArray.add("0");
        cmdArray.add("-f");
        cmdArray.add("concat");
        cmdArray.add("-i");
        cmdArray.add(concatFile);
        cmdArray.add("-c");
        cmdArray.add("copy");
        cmdArray.add("-preset");
        cmdArray.add("ultrafast");
        cmdArray.add(dstFile);

        exeCmd(cmdArray);

        FileUtil.delete(new File(tmpFile));
        FileUtil.delete(new File(concatFile));
        DLog.i(TAG, "emptyvideo time:" + (System.currentTimeMillis() - time));
    }

    public static void makeScaleVideo(String srcFile, String dstFile, int width, int height) {
        ArrayList<String> cmdArray = new ArrayList<>();
        cmdArray.add(sFfmpegFile);
        cmdArray.add("-i");
        cmdArray.add(srcFile);
        cmdArray.add("-crf");
        cmdArray.add("27");
        cmdArray.add("-s");
        cmdArray.add(width + "x" + height);
//        cmdArray.add("-an");
        cmdArray.add("-movflags");
        cmdArray.add("faststart");
        cmdArray.add("-preset");
        cmdArray.add("ultrafast");
        cmdArray.add(dstFile);

        exeCmd(cmdArray);
    }

    /**
     * 图片+多视频合并，外部需要先将图片合成为bitmap(顺序播放时，需要先把各视频的第一帧先合成到bitamp中)，方法内部将多视频合并在图片上
     * 耗时方法，必须在异步线程执行
     * @param bg                视频合成在此bitmap中，bitmap大小需为实际合并后的大小
     * @param cover             顶部遮罩图，可为空
     * @param playTogether      是否同时播放
     * @param combineBeanList   需要合成的视频列表数据
     * @param dstFile           文件保存路径
     */
    public static void mergeVideo(Bitmap bg, Bitmap cover, boolean playTogether,
                                  List<CombineBean> combineBeanList, String dstFile,
                                  boolean highDefinitionBg) {
        long time = System.currentTimeMillis();
        ArrayList<String> tmpFileList = new ArrayList<>();

        String imgFile = getTmpDir() + "img" + System.currentTimeMillis() + IMG_FILE_SUFFIX;
        tmpFileList.add(imgFile);
        // 合成时间跟底图图片质量有关
        if (highDefinitionBg) {
            BitmapUtils.saveBitmap(bg, imgFile, 100);
        } else {
            BitmapUtils.saveBitmap(bg, imgFile, 5);
        }
        BitmapUtils.saveImgSizeExif(imgFile, bg.getWidth(), bg.getHeight());
        CombineBean bgBean = new CombineBean();
        bgBean.type = CombineBean.TYPE_IMG;
        bgBean.file = imgFile;
        mergeVideo(bgBean, cover, playTogether, combineBeanList, dstFile);
        // 清除临时文件
        deleteFiles(tmpFileList);

        DLog.i(TAG, "mergeVideo time:" + (System.currentTimeMillis() - time));
    }

    /**
     * 图片+多视频合并，外部需要先将图片合成为bitmap(顺序播放时，需要先把各视频的第一帧先合成到bitamp中)，方法内部将多视频合并在图片上
     * 耗时方法，必须在异步线程执行
     * @param bgBean            背景bean，可为图片或视频，大小需为实际合并后的大小
     * @param cover             顶部遮罩图，可为空
     * @param playTogether      是否同时播放
     * @param combineBeanList   需要合成的视频列表数据
     * @param dstFile           文件保存路径
     */
    public static void mergeVideo(CombineBean bgBean, Bitmap cover, boolean playTogether,
                                  List<CombineBean> combineBeanList, String dstFile) {
        long time = System.currentTimeMillis();
        ArrayList<String> tmpFileList = new ArrayList<>();
        ArrayList<String> cmdArray = new ArrayList<>();
        cmdArray.add(sFfmpegFile);
//        cmdArray.add("-loglevel");
//        cmdArray.add("quiet");

        // 顺序播放时，需要把时间长的放在前面，否则短的播放完后，长的也停止播放
        if (playTogether) {
            Collections.sort(combineBeanList, new Comparator<CombineBean>() {

                @Override
                public int compare(CombineBean o1, CombineBean o2) {
                    return ((Integer) o2.srcDuring).compareTo(o1.srcDuring);
                }
            });
        }

        if (playTogether && bgBean.type == CombineBean.TYPE_IMG) {
            cmdArray.add("-loop");
            cmdArray.add("1");
        }
        cmdArray.add("-i");
        cmdArray.add(bgBean.file);

        // 合并后视频时长
        int during = 0;
        // 配置输入视频延时
        for (CombineBean input : combineBeanList) {
            if (input.srcDuring + input.startOffset > during) {
                during = input.srcDuring + input.startOffset;
            }
            if (!playTogether) {
                // 顺序播放，配置视频开始播放延时
                int offsetTime = input.startOffset;
                if (offsetTime > 0) {
                    cmdArray.add("-itsoffset");
                    cmdArray.add(buildTimeString(offsetTime));
                }
            }
            cmdArray.add("-i");
            cmdArray.add(input.file);
        }
        if (bgBean.srcDuring > during) {
            during = bgBean.srcDuring;
        }
        // 如果有顶部遮罩
        String coverFile = null;
        if (cover != null) {
            coverFile = getTmpDir() + "img_vover" + System.currentTimeMillis() + IMG_FILE_SUFFIX;
            tmpFileList.add(coverFile);
            cmdArray.add("-i");
            BitmapUtils.savePngBitmap(cover, coverFile, 90);
//            BitmapUtils.saveImgSizeExif(coverFile, cover.getWidth(), cover.getHeight());
            cmdArray.add(coverFile);
        }
        if (playTogether) {
            // 视频合成滤镜
            cmdArray.add("-filter_complex");
            cmdArray.add(buildVideoFilter(coverFile, playTogether, combineBeanList, false, "[0]", bgBean.type == CombineBean.TYPE_IMG));

            cmdArray.add("-c:v");
            cmdArray.add("libx264");
            cmdArray.add("-preset");
            cmdArray.add("ultrafast");
            cmdArray.add("-t");
            cmdArray.add(buildTimeString(during));
            cmdArray.add(dstFile);
            exeCmd(cmdArray);
        } else {
            String bgChannel = "[0]";
            if (combineBeanList.size() > 1 && bgBean.type == CombineBean.TYPE_IMG) {
                for (int i = 1; i < combineBeanList.size(); ++i) {
                    CombineBean input = combineBeanList.get(i);
                    cmdArray.add("-i");
                    cmdArray.add(input.file);
                }
                bgChannel = "[bg" + (combineBeanList.size() - 1) + "]";
            }
            // 顺序播放，先要将视频无声合成，再分别将声音单独合成后，再与视频一起合成
            String slientVideoPath = getTmpDir() + "sv" + System.currentTimeMillis() + VIDEO_FILE_SUFFIX;
            tmpFileList.add(slientVideoPath);
            // 合成无声音视频
            cmdArray.add("-filter_complex");
            String filter = buildVideoFilter(coverFile, playTogether, combineBeanList, true, bgChannel, bgBean.type == CombineBean.TYPE_IMG);
            if (combineBeanList.size() > 1 && bgBean.type == CombineBean.TYPE_IMG) {
                String bgFilter = buildBgFilter(combineBeanList, cover != null);
                cmdArray.add(bgFilter + filter);
            } else {
                cmdArray.add(filter);
            }
            cmdArray.add("-c:v");
            cmdArray.add("libx264");
            cmdArray.add("-preset");
            cmdArray.add("ultrafast");
            cmdArray.add("-threads");
            cmdArray.add("0");
            cmdArray.add("-t");
            cmdArray.add(buildTimeString(during));
            cmdArray.add("-an");
            cmdArray.add("-y");
            cmdArray.add(slientVideoPath);
            exeCmd(cmdArray);
            // 将音频提取出来再进行合并
            int audioInputCount = 0;
            for (CombineBean input : combineBeanList) {
                if (!input.slient && input.hasAudio) {
                    ++audioInputCount;
                }
            }
            if (audioInputCount == 0) {
                // 都为静音，直接copy静音视频到目标路径
                try {
                    FileUtil.copyFile(slientVideoPath, dstFile, true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                String audioOut;
                audioOut = getTmpDir() + "audio" + System.currentTimeMillis() + AUDIO_FILE_SUFFIX;
                tmpFileList.add(audioOut);
                concatAudio(combineBeanList, audioOut);
                // 使用MediaMuxer合并音视频，加快合成速度
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                    AVMuxer.muxing(ComponentContext.getContext(), slientVideoPath, audioOut, dstFile, 0);
                } else {
                    mergeAV(slientVideoPath, audioOut, dstFile);
                }
            }
        }

        // 清除临时文件
        deleteFiles(tmpFileList);

        DLog.i(TAG, "mergeVideo time:" + (System.currentTimeMillis() - time));
    }

    private static void concatAudio(List<CombineBean> combineBeanList, String output) {
        ArrayList<String> cmdArray = new ArrayList<>();
        cmdArray.add(sFfmpegFile);
//        cmdArray.add("-loglevel");
//        cmdArray.add("quiet");

        // 合并后视频时长
        int during = 0;
        int offset = 0;
        ArrayList<CombineBean> tmpList = new ArrayList<>();
        for (CombineBean input : combineBeanList) {
            if (input.srcDuring + input.startOffset > during) {
                during = input.srcDuring + input.startOffset;
            }
            if (input.startOffset > offset) {
                // 增加gif元素后，顺序播放时，中间如果插了一个gif，音频需要插入一个等时长的空音频
                CombineBean tmp = new CombineBean();
                tmp.srcDuring = input.startOffset - offset;
                tmp.hasAudio = false;
                tmp.file = com.photoeditor.demo.util.FileUtil.BASIC_VIDEO_50_FILE_PATH;
                cmdArray.add("-i");
                cmdArray.add(tmp.file);
                tmpList.add(tmp);
            }
            offset = input.startOffset + input.srcDuring;
            cmdArray.add("-i");
            cmdArray.add(input.file);
            tmpList.add(input);
        }
        cmdArray.add("-filter_complex");
        cmdArray.add(buildAudioFilter(tmpList));
        cmdArray.add("-map");
        cmdArray.add("[out]");
        cmdArray.add("-c:v");
        cmdArray.add("libx264");
        cmdArray.add("-preset");
        cmdArray.add("ultrafast");
        cmdArray.add("-t");
        cmdArray.add(buildTimeString(during));
        cmdArray.add("-y");
        cmdArray.add(output);
        exeCmd(cmdArray);
    }

    private static String buildAudioFilter(List<CombineBean> combineBeanList) {
        StringBuilder tmp = new StringBuilder();
        ArrayList<String> concatInput = new ArrayList<>(combineBeanList.size());
        for (int i = 0; i < combineBeanList.size(); ++i) {
            CombineBean bean = combineBeanList.get(i);
            if (!bean.hasAudio) {
                tmp.append("aevalsrc=0:d=");
                tmp.append(bean.srcDuring);
                tmp.append("[s");
                tmp.append(i);
                tmp.append("];");
                concatInput.add("[s" + i + "]");
            } else if (bean.slient) {
                tmp.append("[");
                tmp.append(i);
                tmp.append(":a]volume=0.0");
                tmp.append("[s");
                tmp.append(i);
                tmp.append("];");
                concatInput.add("[s" + i + "]");
            } else {
                concatInput.add("[" + i + ":a]");
            }
        }
        for (int i = 0; i < concatInput.size(); ++i) {
            tmp.append(concatInput.get(i));
        }
        tmp.append("concat=n=");
        tmp.append(concatInput.size());
        tmp.append(":v=0:a=1[out]");
        String ret = tmp.toString();
        DLog.i(TAG, "audiofilter:" + ret);
        return ret;
    }

    private static String buildVideoFilter(String coverFilePath, boolean playTogether, List<CombineBean> combineBeanList, boolean ignoreAudio, String bgChannel, boolean imgBg) {
        // 注意：图片是第一个的输入文件
        StringBuilder tmp = new StringBuilder();
        // 先对各视频进行缩放裁剪
        for (int i = 0; i < combineBeanList.size(); ++i) {
            CombineBean bean = combineBeanList.get(i);
            // [1:v]setpts=PTS-STARTPTS,scale=-1:702,crop=228:702:149:0[inner1];
            int width = (int) (bean.srcWidth * bean.scale);
            int height = (int)(bean.srcHeight * bean.scale);
            tmp.append("[" + (i +  1) + ":v]");
            if (playTogether && imgBg) {
                tmp.append("setpts=PTS-STARTPTS,");
            }
            tmp.append("scale=");
            if (width > height) {
                tmp.append(-1);
                tmp.append(":");
                tmp.append(height);
            } else {
                tmp.append(width);
                tmp.append(":");
                tmp.append(-1);
            }
            int verifyW = Math.min(bean.clipRect.width(), width);
            int verifyH = Math.min(bean.clipRect.height(), height);
            tmp.append(",");
            tmp.append("crop=");
            tmp.append(verifyW);
            tmp.append(":");
            tmp.append(verifyH);
            tmp.append(":");
            tmp.append(bean.clipRect.left);
            tmp.append(":");
            tmp.append(bean.clipRect.top);
            tmp.append("[inner" + (i + 1) + "];");
        }
        // 再对各视频进行合并
        // 以图片为底图进行合并
        String input = bgChannel;
        for (int i = 0; i < combineBeanList.size(); ++i) {
            CombineBean bean = combineBeanList.get(i);
            // [0][inner1]overlay=245:8[tmp1];[tmp1][inner2]overlay=482:8[tmp2];
            // [tmp2][inner3]overlay=8:8[tmp3];[tmp3][4]overlay=605:612;
            tmp.append(input);
            tmp.append("[inner" + (i + 1) + "]");
            tmp.append("overlay=");
            tmp.append(bean.pos.x);
            tmp.append(":");
            tmp.append(bean.pos.y);
            if (i != combineBeanList.size() - 1 || !TextUtils.isEmpty(coverFilePath)) {
                input = "[tmp" + (i + 1) + "]";
                tmp.append(input);
                tmp.append(";");
            } else if (i == combineBeanList.size() - 1 || TextUtils.isEmpty(coverFilePath)){
                tmp.append(";");
            }
        }
        if (!TextUtils.isEmpty(coverFilePath)) {
            tmp.append(input);
            tmp.append("[" + (combineBeanList.size() + 1) + "]");
            tmp.append("overlay=0:0;");
        }
        if (!ignoreAudio) {
            // 处理音频，默认合并后只有第一个音频有声音
            // [1:a]volume=1.0[a1];[2:a]volume=0.0[a2];[3:a]volume=0.0[a3];[a1][a2][a3]amix=inputs=3:duration=longest:dropout_transition=3
            int audioCount = 0;
            boolean hasAudio = false;
            for (int i = 0; i < combineBeanList.size(); ++i) {
                CombineBean bean = combineBeanList.get(i);
                if (bean.hasAudio) {
                    hasAudio = true;
                    break;
                }
            }
            if (hasAudio) {
                for (int i = 0; i < combineBeanList.size(); ++i) {
                    CombineBean bean = combineBeanList.get(i);
                    if (bean.hasAudio) {
                        tmp.append("[");
                        tmp.append((i + 1));
                        tmp.append(":a]volume=");
                        if (bean.slient) {
                            tmp.append("0.0");
                        } else {
                            tmp.append("1.0");
                        }
                        tmp.append("[a");
                        tmp.append((i + 1));
                        tmp.append("];");
                        ++audioCount;
                    }
                }
                for (int i = 0; i < combineBeanList.size(); ++i) {
                    CombineBean bean = combineBeanList.get(i);
                    if (bean.hasAudio) {
                        tmp.append("[a");
                        tmp.append((i + 1));
                        tmp.append("]");
                    }
                }
                tmp.append("amix=inputs=");
                tmp.append(audioCount);
                tmp.append(":duration=longest:dropout_transition=3;");
            }
        }
        String ret = tmp.substring(0, tmp.length() - 1);
        DLog.i(TAG, "videofilter:" + ret);
        return ret;
    }

    private static String buildBgFilter(List<CombineBean> combineBeanList, boolean hasCover) {
        StringBuilder tmp = new StringBuilder();
        String bgChannel = "[0]";
        for (int i = 1; i < combineBeanList.size(); ++i) {
            CombineBean bean = combineBeanList.get(i);
            tmp.append("[");
            tmp.append(combineBeanList.size() + i + (hasCover ? 1 : 0));
            tmp.append("]select='eq(n\\,1)',scale=");
            int width = (int) (bean.srcWidth * bean.scale);
            int height = (int)(bean.srcHeight * bean.scale);
            if (width > height) {
                tmp.append(-1);
                tmp.append(":");
                tmp.append(height);
            } else {
                tmp.append(width);
                tmp.append(":");
                tmp.append(-1);
            }
            tmp.append(",");
            tmp.append("crop=");
            tmp.append(bean.clipRect.width());
            tmp.append(":");
            tmp.append(bean.clipRect.height());
            tmp.append(":");
            tmp.append(bean.clipRect.left);
            tmp.append(":");
            tmp.append(bean.clipRect.top);
            tmp.append("[ov");
            tmp.append(i);
            tmp.append("];");
            tmp.append(bgChannel);
            tmp.append("[ov");
            tmp.append(i);
            tmp.append("]");
            tmp.append("overlay=");
            tmp.append(bean.pos.x);
            tmp.append(":");
            tmp.append(bean.pos.y);
            bgChannel = "[bg" + i + "]";
            tmp.append(bgChannel);
            tmp.append(";");
        }
        String ret = tmp.toString();
        DLog.i(TAG, "bgfilter:" + ret);
        return ret;
    }

    /**
     * 音频+视频合并
     * @param videoFile
     * @param audioFile
     * @param dstFile
     */
    public static void mergeAV(String videoFile, String audioFile, String dstFile) {
        ArrayList<String> cmdArray = new ArrayList<>();
        cmdArray.add(sFfmpegFile);
        cmdArray.add("-i");
        cmdArray.add(videoFile);
        cmdArray.add("-i");
        cmdArray.add(audioFile);
        cmdArray.add("-movflags");
        cmdArray.add("faststart");
        cmdArray.add("-preset");
        cmdArray.add("ultrafast");
        cmdArray.add(dstFile);
        exeCmd(cmdArray);
    }

    /**
     * 合并多段音频（非拼接）
     * @param dstFile
     * @param audioFile
     */
    public static void mergeAudio(String dstFile, String... audioFile) {
        ArrayList<String> cmdArray = new ArrayList<>();
        cmdArray.add(sFfmpegFile);
        for (String input : audioFile) {
            cmdArray.add("-i");
            cmdArray.add(input);
        }
        cmdArray.add("-filter_complex");
        cmdArray.add("amix=inputs=" + audioFile.length + ":duration=longest:dropout_transition=0");
        cmdArray.add(dstFile);
        exeCmd(cmdArray);
    }

    /**
     * 去除视频中音频
     * @param srcFile
     * @param dstFile
     */
    public static void extractVideo(String srcFile, String dstFile) {
        ArrayList<String> cmdArray = new ArrayList<>();
        cmdArray.add(sFfmpegFile);
        cmdArray.add("-i");
        cmdArray.add(srcFile);
        cmdArray.add("-acodec");
        cmdArray.add("copy");
        cmdArray.add("-an");
        cmdArray.add(dstFile);
        exeCmd(cmdArray);
    }

    /**
     * 从视频中提取音频，并在音频前延时指定时间
     * @param srcFile       视频文件
     * @param dstFile       输出音频文件
     * @param offsetTime    音频延时播放时间（单位：毫秒）
     */
    public static void extractAudio(String srcFile, String dstFile, long offsetTime) {
        String tmp;
        if (offsetTime == 0) {
            tmp = dstFile;
        } else {
            tmp = getTmpDir() + (System.currentTimeMillis() + 1) + ".aac";
        }
        // 先提取音频，再对视频插入延时时间
        ArrayList<String> cmdArray = new ArrayList<>();
        cmdArray.add(sFfmpegFile);
        cmdArray.add("-i");
        cmdArray.add(srcFile);
        cmdArray.add("-acodec");
        cmdArray.add("copy");
        cmdArray.add("-vn");
        cmdArray.add(tmp);
        exeCmd(cmdArray);

        if (offsetTime > 0) {
            cmdArray.clear();
            cmdArray.add(sFfmpegFile);
            cmdArray.add("-i");
            cmdArray.add(tmp);
            cmdArray.add("-filter_complex");
            cmdArray.add("adelay=" + offsetTime + "|" + offsetTime);
            cmdArray.add(dstFile);
            exeCmd(cmdArray);
            FileUtil.delete(new File(tmp));
        }

    }

    /**
     * 耗时方法，需要放在异步线程
     * @param cmd
     */
    public static void exeCmd(String[] cmd) {
        if (TextUtils.isEmpty(sFfmpegFile) || cmd == null || cmd.length < 1) {
            return;
        }
        long time = System.currentTimeMillis();
        String command = "";
        if (cmd.length > 1) {
            StringBuilder builder = new StringBuilder();
            for (String tmp : cmd) {
                builder.append(tmp);
                builder.append(" ");
            }
            command = builder.substring(0, builder.length() - 1);
        } else if (cmd.length == 1) {
            command = cmd[0];
        }
        checkFileInit();
        CommandExecution.CommandResult result = CommandExecution.execCommand(command, false);
        if (result != null && result.result == 1) {
            String error = result.errorMsg;
            // 只统计后面错误信息
            if (!TextUtils.isEmpty(error) && error.length() > 245) {
                error = error.substring(error.length() - 245, error.length());
            }
            String output = "";
            if (cmd.length > 1) {
                output = cmd[cmd.length - 1];
            }
            File file = new File(output);
            String simpleCmd = command.replace(sFfmpegFile, "");
            StatisticsUtils.statisticsFfmpegError(error, simpleCmd, file != null && file.exists(), new File(sFfmpegFile).canExecute());

            if (!TextUtils.isEmpty(error) && error.contains("unused DT entry")) {
                StatisticsUtils.statisticsFfmpegDTEntryError(error, simpleCmd, file != null && file.exists(), new File(sFfmpegFile).canExecute());
            }
            if (!TextUtils.isEmpty(error) && error.contains("No such file or directory")) {
                boolean findNoFile = false;
                for (int i = 0; i < cmd.length; ++i) {
                    String tmp = cmd[i];
                    if (tmp.equals("-i") && i < cmd.length - 1) {
                        String fileCmd = cmd[i + 1];
                        if (!TextUtils.isEmpty(fileCmd) && !new File(fileCmd).exists()) {
                            StatisticsUtils.statisticsFfmpegNoFileError(error, simpleCmd, file != null && file.exists(), new File(sFfmpegFile).canExecute(), fileCmd);
                            findNoFile = true;
                        }
                    }
                }
                if (!findNoFile) {
                    StatisticsUtils.statisticsFfmpegNoFileError(error, simpleCmd, file != null && file.exists(), new File(sFfmpegFile).canExecute(), "");
                }
            }
        }
        DLog.i(TAG, "cmd " + "time:" + (System.currentTimeMillis() - time) + " result:" + result.result + "  " + result.errorMsg);
    }

    public static void exeCmd(ArrayList<String> cmd) {
        exeCmd(cmd.toArray(new String[cmd.size()]));
    }

    public static void init(Context context) {
        // 清除上次缓存文件
        TMP_DIR = FileUtil.getExternalCacheDir(ComponentContext.getContext(), ".vtmp", true) + File.separator;
        FileUtil.delete(new File(TMP_DIR));
        TMP_DIR = FileUtil.getCacheDir(ComponentContext.getContext(), ".vtmp", true) + File.separator;
        FileUtil.delete(new File(TMP_DIR));
        checkFileInit();
        return;
    }

    private static String getTmpDir() {
        try {
            TMP_DIR = FileUtil.getExternalCacheDir(ComponentContext.getContext(), ".vtmp", true) + File.separator;
        } catch (Exception e) {
            // 外部存储出问题，保存在data/data中
            TMP_DIR = FileUtil.getCacheDir(ComponentContext.getContext(), ".vtmp", true) + File.separator;
        }
        return TMP_DIR;
    }

    public static void checkFileInit() {
        Context context = ComponentContext.getContext();
        if (isArm()) {
            // 将ffmpeg拷贝到手机data/data
            File output = new File(context.getCacheDir(), "ffmepeg.lut");
            sFfmpegFile = output.getAbsolutePath();
            if (output.exists() && output.canExecute()) {
                return ;
            }
            FileUtil.copyAssets(context, "ffmepeg.lut", sFfmpegFile);
//            copyAssets(context, "lut", output, "755");
            if (output.exists()) {
                if (!output.canExecute()) {
                    boolean canExec = output.setExecutable(true);
                    if (canExec) {
                        return;
                    }
                }
            }
            // 写入data/data失败时，保存到存储卡
            String storageDir = FileUtil.getExternalCacheDir(ComponentContext.getContext(), ".lut", true);
            sFfmpegFile = storageDir + File.separator + "ffmepeg.lut";
            FileUtil.copyAssets(context, "ffmepeg.lut", sFfmpegFile);
            output = new File(sFfmpegFile);
//            copyAssets(context, "lut", output, "755");
            if (!output.exists()) {
                if (!output.canExecute()) {
                    boolean canExec = output.setExecutable(true);
                    if (canExec) {
                        return;
                    }
                }
                // ffmpeg无法写入到本地时，添加出错统计
                StatisticsUtils.statisticsMakeVideoError("ffmpeg save error", 0, 0);
            }
        }
    }

    public static boolean isArm() {
        boolean ret = false;
        if (Build.CPU_ABI.contains("arm")) {
            ret = true;
        }
        return ret;
    }

    /**
     *
     * @param time 单位ms
     * @return
     */
    private static String buildTimeString(int time) {
        int offsetSecond = time / 1000;
        int offsetMs = time % 1000;
        return String.format("%d.%03d", offsetSecond, offsetMs);
    }

    public static void deleteFiles(final List<String> path) {
        ThreadPool.runOnNonUIThread(new Runnable() {
            @Override
            public void run() {
                for (String tmp : path) {
                    FileUtil.delete(new File(tmp));
                }
            }
        }, 2000);
    }

    private static void copyAssets(Context context, String assetsFilename, File file, String mode) {
        try {
            AssetManager manager = context.getAssets();
            final InputStream is = manager.open(assetsFilename);
            copyFile(file, is, mode);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void copyFile(File file, InputStream is, String mode) throws IOException, InterruptedException {
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        final String abspath = file.getAbsolutePath();
        final FileOutputStream out = new FileOutputStream(file);
        byte buf[] = new byte[1024];
        int len;
        while ((len = is.read(buf)) > 0) {
            out.write(buf, 0, len);
        }
        out.close();
        is.close();
        Runtime.getRuntime().exec("chmod " + mode + " " + abspath).waitFor();
    }

}
